package com.jshoperxms.mongo.dao.impl;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.Resource;


import com.jshoperxms.mongo.dao.RepositoryDao;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;

import com.mongodb.AggregationOptions;
import com.mongodb.BasicDBObject;
import com.mongodb.Cursor;
import com.mongodb.DBObject;

import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;

@Repository("repositoryDao")
public class RepositoryDaoImpl<T> implements RepositoryDao<T> {
	@Resource
	private MongoTemplate mongoTemplate;

	@Override
	public void insert(T t) {
		mongoTemplate.insert(t);
	}

	@Override
	public void insertAll(List<T> t) {
		mongoTemplate.insertAll(t);
	}

	@Override
	public boolean deleteById(String id, Class<T> t) {
		Criteria criteria = Criteria.where("_id").in(id);
		if (null != criteria) {
			Query query = new Query(criteria);
			List<T> list = this.findAll(query, t);
			if (list != null && list.size() > 0) {
				this.delete(list.get(0));
				return true;
			}
			/*
			 * if(null!=query&&this.findOne(query, t)!=null){
			 * this.delete(this.findOne(query, t)); return true; }
			 */
		}
		return false;
	}

	@Override
	public void delete(T t) {
		mongoTemplate.remove(t);

	}

	@Override
	public T findOne(Query query, Class<T> t) {
		return mongoTemplate.findOne(query, t);
	}

	@Override
	public boolean updateFirst(Query query, Update update, Class<T> t) {
		mongoTemplate.setWriteConcern(WriteConcern.ACKNOWLEDGED);
		WriteResult result = mongoTemplate.updateFirst(query, update, t);
		if (result.getN() == 1) {
			return true;
		}
		return false;
	}

	@Override
	public List<T> findAll(Class<T> t) {
		return mongoTemplate.findAll(t);
	}

	@Override
	public Long getCount(Query query, Class<T> t) {
		return mongoTemplate.count(query, t);
	}

	@Override
	public List<T> findAll(Query query, Class<T> t) {
		return mongoTemplate.find(query, t);
	}

	@Override
	public List<T> findByPage(Query query, int currentPage, int lineSize,
			Class<T> t) {
		query.skip((currentPage - 1) * lineSize);
		query.limit(lineSize);
		return mongoTemplate.find(query, t);
	}

	@Override
	public T findAndModify(Query query, Update update, Class<T> t) {
		return mongoTemplate.findAndModify(query, update, t);
	}

	@Override
	public T findAndRemove(Query query, Class<T> t) {
		return mongoTemplate.findAndRemove(query, t);
	}

	@Override
	public void saveOrUpdate(T t) {
		mongoTemplate.save(t);
	}

	@Override
	public boolean updateMulti(Query query, Update update, Class<T> t) {
		mongoTemplate.setWriteConcern(WriteConcern.ACKNOWLEDGED);
		WriteResult result = mongoTemplate.updateMulti(query, update, t);
		if (result.getN() >= 1) {
			return true;
		}
		return false;
	}

	@Override
	public boolean upsert(Query query, Update update, Class<T> t) {
		mongoTemplate.setWriteConcern(WriteConcern.ACKNOWLEDGED);
		WriteResult result = mongoTemplate.upsert(query, update, t);
		if (result.getN() >= 1) {
			return true;
		}
		return false;
	}

	@Override
	public void drop(Class<T> t) {
		mongoTemplate.dropCollection(t);

	}

	@Override
	public List<DBObject> geoNear(String collection, DBObject query,
			double[] point, int limit, long maxDistance) {
		if (query == null)
			query = new BasicDBObject();

		List<DBObject> pipeLine = new ArrayList<>();
		BasicDBObject aggregate = new BasicDBObject("$geoNear",
				new BasicDBObject("near",
						new BasicDBObject("type", "Point").append(
								"coordinates", point)))
				.append("distanceField", "dist.calculated")
				.append("query", query).append("num", limit)
				.append("maxDistance", maxDistance).append("spherical", true);
		pipeLine.add(aggregate);
		
		Cursor cursor = mongoTemplate.getCollection(collection).aggregate(
				pipeLine, AggregationOptions.builder().build());
		
		List<DBObject> list = new LinkedList<>();
		while (cursor.hasNext()) {
			list.add(cursor.next());
		}
		return list;
	}

	@Override
	public List<DBObject> withinCircle(String collection, String locationField,
			double[] center, long radius, DBObject fields, DBObject query,
			int limit) {
		LinkedList<Object> circle = new LinkedList<>();
		// Set the center coordinate
		circle.addLast(center);
		// Set the radius. unit:meter
		circle.addLast(radius / 6378137.0);

		if (query == null)
			query = new BasicDBObject();
		query.put(locationField, new BasicDBObject("$geoWithin",
				new BasicDBObject("$centerSphere", circle)));
		return mongoTemplate.getCollection(collection).find(query, fields)
				.limit(limit).toArray();
	}

	@Override
	public List<DBObject> nearSphere(String collection, String locationField,
			double[] center, long minDistance, long maxDistance,
			DBObject query, DBObject fields, int limit) {
		if (query == null)
			query = new BasicDBObject();

		query.put(
				locationField,
				new BasicDBObject("$nearSphere", new BasicDBObject("$geometry",
						new BasicDBObject("type", "Point").append(
								"coordinates", center)).append("$minDistance",
						minDistance).append("$maxDistance", maxDistance)));
		return mongoTemplate.getCollection(collection).find(query, fields)
				.limit(limit).toArray();
	}

	@Override
	public List<DBObject> withinPolygon(String collection,
			String locationField, List<double[]> polygon, DBObject fields,
			DBObject query, int limit) {
		if (query == null)
			query = new BasicDBObject();

		List<List<double[]>> polygons = new LinkedList<>();
		polygons.add(polygon);
		query.put(locationField, new BasicDBObject("$geoWithin",
				new BasicDBObject("$geometry", new BasicDBObject("type",
						"Polygon").append("coordinates", polygons))));
		return mongoTemplate.getCollection(collection).find(query, fields)
				.limit(limit).toArray();
	}

	@Override
	public List<DBObject> withinMultiPolygon(String collection,
			String locationField, List<List<double[]>> polygons,
			DBObject fields, DBObject query, int limit) {
		if (query == null)
			query = new BasicDBObject();

		List<List<List<double[]>>> list = new LinkedList<>();
		for (List<double[]> polygon : polygons) {
			List<List<double[]>> temp = new LinkedList<>();
			temp.add(polygon);
			list.add(temp);
		}
		query.put(locationField, new BasicDBObject("$geoWithin",
				new BasicDBObject("$geometry", new BasicDBObject("type",
						"MultiPolygon").append("coordinates", list))));
		return mongoTemplate.getCollection(collection).find(query, fields)
				.limit(limit).toArray();
	}

	@Override
	public List<DBObject> withinBox(String collection, String locationField,
			double[] bottomLeft, double[] upperRight, DBObject fields,
			DBObject query, int limit) {
		if (query == null)
			query = new BasicDBObject();

		LinkedList<double[]> box = new LinkedList<>();
		box.add(bottomLeft);
		box.add(upperRight);

		query.put(locationField, new BasicDBObject("$geoWithin",
				new BasicDBObject("$box", box)));
		return mongoTemplate.getCollection(collection).find(query, fields)
				.limit(limit).toArray();
	}

}
